iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Modern Web

成為Canvas Ninja ! ~ 理解2D渲染的精髓系列 第 15

Day15 - 中場休息時間 - 來看看htmlToCanvas的實作吧 - 成為Canvas Ninja ~ 理解2D渲染的精髓

  • 分享至 

  • xImage
  •  

經過了連續5篇複雜度略高的物理模擬系列,我在想看官們多少會有點疲乏~

所以我在規劃了幾篇『中場休息』系列科普文,用來穿插在主要的chapter之間,

休息是為了走更長遠的路,還有看更多的物理模擬(X

主要內容會講一些比較容易理解~一篇之內就可以講完的案例。

這篇文是『中場休息』系列的第一篇文,我們這次會講講怎麼樣在Canvas上實作 html 轉圖像的功能。

htmlToCanvas實作

大部分人提到htmlToCanvas的實作,應該會直接想到html2Canvas這個著名的NPM包,但是我們秉持著NINJA精神當然不能光會用別人寫的包,所以我們就來看看這個案例的實作原理

其實htmlToCanvas的實作在這一篇MDN的舊版文章(已經被封存)裡面有提到過做法

這邊直接把源碼貼上來:

<canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>
var canvas = document.getElementById('canvas');
var ctx    = canvas.getContext('2d');

var data   = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
               '<foreignObject width="100%" height="100%">' +
                 '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
                   '<em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span>' +
                 '</div>' +
               '</foreignObject>' +
             '</svg>';

var DOMURL = window.URL || window.webkitURL || window;// 這是一個防呆,因為不同瀏覽器的createObjectURL方法可能存在於不同對象底下。

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

img.onload = function () {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);//這個api是用來銷毀已經用不到的URL,避免記憶體消耗
}

img.src = url;

codepen連結: https://codepen.io/mizok_contest/pen/RwgdpgR

在上面這個案例我們可以看到,html to canvas的實作流程大致如下:

  1. 把dom element用 svg 的 foreignObject tag 塞到svg內部
  2. Blob類的建構式去把svg字串轉換成Blob物件
  3. URL.createObjectURL(Blob) 去取得轉化出來的Blob物件的URL
  4. 把取得的URL作為src賦予給image,然後在image on load的時候用ctx.drawImage畫出來

這邊我們就每個步驟稍微說明一下~

為什麼要用foreignObject?

首先我們來講講foreignObject 是什麼。

有自己寫過svg的同學應該很清楚,svg的原生元素是沒有辦法做文字段落自動換行的,一般要換行的話,我們只能透過把斷行的部分寫成多個<tspan>,然後手動指定tspan的座標值,讓他看起來像換到下一行

(聽起來很笨,但是確實就是這樣)

延伸閱讀:tspan 在MDN上的介紹頁面: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan

foreignObject的存在意義其實就是可以讓我們在svg的XML namespace(命名空間)底下去把不同namespace結構語言給渲染出來,像這樣透過使用foreignObject,我們就可以輕鬆地在svg內部實現文字換行~

XML 指的是結構性語言,html/svg都是一種XML,但是他們只能在特定的namespace底下被渲染,不同的namespace底下會有不同的渲染邏輯~

透過使用foreignObjecthtml的內容埋進去svg裡面,這樣我們就得到了一張具有html外觀的svg了~大概可以這樣理解。

Blob 是什麼?為什麼要用它?

Blob其實是一種 類檔案(File-like)物件

舉個例來說~我們常常看到有些網頁會有利用<input type="file">檔案上傳的功能,
這些input在on change時接到的東西其實就是Blob的一種。

而我們這邊則是透過手動把svg字串傳到Blob類的建構式,來建立一個全新的Blob實例。

new Blob(array, options);

Blob 的第一個參數固定要傳一個陣列,而陣列的內容可以允許傳入字串(也可以傳入其他的東西,可以讀MDN上的解釋)

延伸閱讀: MDN 上關於 Blob()的頁面:https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob

之所以用Blob 是因為後面需要用createObjectURL(Blob) 來取得Blob實例的URL。

延伸閱讀: MDN 上關於 createObjectURL(Blob)的頁面:https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL

如果不想要用Blob來取得image src url,其實也是可以直接把埋入foreignObjectsvg string直接寫入<img>src attribute,就像這樣:

<img width="600" height="450" src='data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="100%" height="100%"><body xmlns="http://www.w3.org/1999/xhtml"><span style="color:red">123123123</span></body></foreignObject></svg>'>

drawImage部分

其實ctx.drawImage我們之前有提到過(在像素操作概論的篇章)

可以看這邊

他就是2DContext所提供的一個用來把圖片畫到Canvasapi

而這邊就是把前面拿到的Blob url 去賦予給 Image.src,然後再把這張圖片給畫出來~

小結

到這邊為止我們大概可以理解htmlToCanvas的實作,但是以實際場景來講其實很多時候不會像我們上面給的範例一樣這麼簡單。
打個比方,例如典型的跨域問題,導致我們在程序中無法順利取得正確的圖片/樣式表,除此之外,因為為了要防堵資安漏洞,利用foreignObject 去取得html的渲染畫面這一操作其實有很多限制,例如:

  • 只能使用內置樣式
  • 不能在foreignObject中引入js文件,這意味著有些透過js生成的樣式變成需要手動賦予到foreignObjecthtml元素上
  • ...,etc.

而為了應對各種複雜的截圖需求,才會有了html2Canvas這樣的插件,這個插件實際上是用了很多比較tricky的方法去繞過防堵機制來達成部分因為上述限制而難以實行的問題,但是也因為這樣這個插件當然也就會有被認定為bug的狀況。

到這邊為止是這次的html2Canvas實作介紹~希望大家喜歡 :D


上一篇
Day14 - 物理模擬篇 - 彈跳球世界IV(補完篇) - 成為Canvas Ninja ~ 理解2D渲染的精髓
下一篇
Day16 - 物理模擬篇 - 彈力、引力與磁力I - 成為Canvas Ninja ~ 理解2D渲染的精髓
系列文
成為Canvas Ninja ! ~ 理解2D渲染的精髓31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言